package com.jxpanda.spring.module.auth.core.authentication.manager;

import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.jxpanda.infrastructure.core.tollkit.StringKit;
import com.jxpanda.infrastructure.core.tollkit.json.JsonKit;
import com.jxpanda.infrastructure.spring.toolkit.ReactiveHttpKit;
import com.jxpanda.spring.module.auth.core.authentication.token.CollaborativeAuthenticationToken;
import com.jxpanda.spring.module.auth.core.authentication.token.OAuth2UserCollaborativeAuthenticationToken;
import com.jxpanda.spring.module.auth.core.user.WechatOAuth2User;
import com.jxpanda.spring.module.auth.endpoint.OAuth2Request;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.springframework.security.oauth2.client.registration.ClientRegistration;
import reactor.core.publisher.Mono;

/**
 * 微信小程序认证管理器
 */
public class WechatMiniProgramReactiveAuthenticationManager implements CollaborativeReactiveAuthenticationManager {

    private static final String DEFAULT_MINI_PROGRAM_AUTHORIZATION_URL = "https://api.weixin.qq.com/sns/jscode2session";

    @Override
    public Mono<CollaborativeAuthenticationToken> authenticate(CollaborativeAuthenticationToken authenticationToken) {
        return Mono.defer(() -> {
            OAuth2Request oAuth2Request = authenticationToken.getOAuth2Request();
            ClientRegistration clientRegistration = authenticationToken.getClientRegistration();
            return fetchJsCode2Session(clientRegistration, oAuth2Request.getCode())
                    .map(jsCode2Session -> buildAuthenticationToken(jsCode2Session, oAuth2Request, clientRegistration));
        });
    }

    private static OAuth2UserCollaborativeAuthenticationToken buildAuthenticationToken(JsCode2Session jsCode2Session, OAuth2Request oAuth2Request, ClientRegistration clientRegistration) {
        WechatOAuth2User wechatOauth2User = new WechatOAuth2User(jsCode2Session.getOpenId(), jsCode2Session.getUnionId(), clientRegistration.getProviderDetails().getUserInfoEndpoint().getUserNameAttributeName());
        return new OAuth2UserCollaborativeAuthenticationToken(oAuth2Request, clientRegistration, wechatOauth2User);
    }

    private static Mono<JsCode2Session> fetchJsCode2Session(ClientRegistration clientRegistration, String code) {
        String authorizationUrl = StringKit.takeFirstNotBlank(clientRegistration.getProviderDetails().getAuthorizationUri(), DEFAULT_MINI_PROGRAM_AUTHORIZATION_URL);
        return ReactiveHttpKit.get(authorizationUrl)
                .queryHandler(query -> {
                    query.put("appid", clientRegistration.getClientId());
                    query.put("secret", clientRegistration.getClientSecret());
                    query.put("js_code", code);
                    query.put("grant_type", "authorization_code");
                }).build()
                .execute(bodyData -> JsonKit.fromJson(bodyData, JsCode2Session.class));
    }

    @Data
    @NoArgsConstructor
    private static class JsCode2Session {

        /**
         * openid
         */
        @JsonProperty("openid")
        private String openId;
        /**
         * sessionKey
         */
        @JsonProperty("session_key")
        private String sessionKey;
        /**
         * unionid
         */
        @JsonProperty("unionid")
        private String unionId;
        /**
         * errcode
         */
        @JsonProperty("errcode")
        private Integer errCode;
        /**
         * errmsg
         */
        @JsonProperty("errmsg")
        private String errMsg;

        @JsonCreator
        public JsCode2Session(
                @JsonProperty("errcode") Integer errCode,
                @JsonProperty("errmsg") String errMsg,
                @JsonProperty("session_key") String sessionKey,
                @JsonProperty("openid") String openId,
                @JsonProperty("unionid") String unionId) {
            this.errCode = errCode;
            this.errMsg = errMsg;
            this.sessionKey = sessionKey;
            this.openId = openId;
            this.unionId = unionId;
        }

    }

}
